using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Reflection;
using System.Diagnostics;

using Microsoft.Practices.EnterpriseLibrary.Logging;
using Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners;

using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;   // for ConfigurationElementType
using Microsoft.Practices.EnterpriseLibrary.Logging.Configuration;  // for CustomTraceListenerData

namespace SBPweb.Logging
{
    /// <summary>
    /// EntLib xml based listener.
    /// </summary>
    [ConfigurationElementType(typeof(CustomTraceListenerData))]
    public class LogXmlTraceListener : EntLibFileTraceListener
    {
        /// <summary>
        /// Opens a listener.
        /// </summary>
        /// <remarks>
        /// Opens the resources those will be used by this listener. public put not supposed to be called from outside EntLib.
        /// The exact behavior is depends on if appending is preferred over truncation (see <see cref="EntLibFileTraceListener.Append"/>) or not or
        /// if a file with the same name is already exists or not. If the file exists and appending is preferred then the underlaying file will be oppened
        /// for appending and closing xml tags will be handled as necessary. If the file exists and appending is not preferred then the file will be truncated.
        /// Otherwise a new file will be created. A run tag will be written as part of file stream initializaton which contains several attributes about
        /// the initialization environment and reasons.
        /// This method supports the EntLib infrastructure.
        /// </remarks>
        public override void Open()
        {
            FileStream fs;

            if (File.Exists(mFilePath))
            {
                if (mAppend)
                {
                    // Reopen xml

                    // Remove closing

                    fs = new FileStream(mFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.Read);

                    // Finding </log>

                    fs.Seek(0, SeekOrigin.End);

                    int i = 0;
                    char chr = ' ';
                    byte[] bytearray = new byte[1];

                    // Find mathcing position
                    do
                    {
                        --i;

                        try
                        {
                            fs.Seek(i, SeekOrigin.End);

                            bytearray[0] = (byte)fs.ReadByte();

                            chr = Encoding.UTF8.GetChars(bytearray)[0];
                        }
                        catch (IOException)
                        {
                            // Corrupt logfile (since it is too short or full of with white spaces)

                            fs.SetLength(0);
                            break;
                        }
                    } while (Char.IsWhiteSpace(chr));

                    if (!Char.IsWhiteSpace(chr))
                    {
                        // Match with </log>
                        bytearray = new byte[6];
                        String str;
                        try
                        {
                            fs.Seek(i - 5, SeekOrigin.End);

                            fs.Read(bytearray, 0, 6);

                            str = Encoding.UTF8.GetString(bytearray);
                            if (str == "</log>")
                            {
                                // The end of the file is closed - truncate the file before that tag

                                fs.SetLength(fs.Position - 6);
                            }
                            else if (str == "</run>")
                            {
                                fs.Seek(0, SeekOrigin.End); // Back to the end
                            }
                            else
                            {
                                // The end of the file is open - now we close the previous run

                                fs.Seek(0, SeekOrigin.End);
                                fs.Write(Encoding.UTF8.GetBytes("</run>\r\n"), 0, 8);
                                fs.Flush();
                            }
                        }
                        catch (IOException)
                        {
                            // Corrupt logfile (since it is too short or full of with white spaces)

                            fs.SetLength(0);
                        }
                    }
                }
                else
                {
                    // Append is set to be false - truncate file

                    fs = new FileStream(mFilePath, FileMode.Truncate, FileAccess.ReadWrite, FileShare.Read);
                }
            }
            else
            {
                // New log xml - create file

                fs = new FileStream(mFilePath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
            }

            // Temporary reference holder to lock on
            StreamWriter sw = new StreamWriter(fs);
            //sw.AutoFlush = true;

            lock (sw)
            {
                mStreamWriter = sw;

                if (fs.Length == 0)
                {
                    // The file is trucated or new - write xml header

                    mStreamWriter.WriteLine("<?xml version=\"1.0\"?>");
                    mStreamWriter.WriteLine("<?xml-stylesheet type='text/xsl' href='EntLibLogger.xsl'?>");
                    mStreamWriter.WriteLine("<log>");
                }

                // Append startup info tag

                String verstr;

                try
                {
                    verstr = Assembly.GetEntryAssembly().GetName().Version.ToString();
                }
                catch
                {
                    verstr = "";
                }

                mStreamWriter.WriteLine(
                    string.Format("<run AppStart=\"{0}\" ExeVersion=\"{1}\" CommandLine=\"{2}\" ComputerName=\"{3}\" UserName=\"{4}\" DebuggerPresent=\"{5}\"  Continue=\"{6}\">",
                    DateTime.UtcNow,
                    verstr,
                    Environment.GetCommandLineArgs()[0].Replace(".vshost", ""),
                    Environment.MachineName,
                    Environment.UserName,
                    Debugger.IsAttached,
                    mTemporary));
            }
        }

        /// <summary>
        /// Closes a trace listener.
        /// </summary>
        /// <remarks>
        /// Closes the resources those were used by this listener. public put not supposed to be called from outside EntLib.
        /// This method supports the EntLib infrastructure.
        /// </remarks>
        public override void Close()
        {
            if (mStreamWriter != null)
                lock (mStreamWriter)
                {
                    mStreamWriter.Write("</run>\r\n</log>");

                    mStreamWriter.Close();

                    mStreamWriter = null;
                    mFilePath = null;
                }
        }

        /// <summary>
        /// Parameterless constructor in order to support ObjectBuilder
        /// </summary>
        public LogXmlTraceListener()
            : base()
        { }
    }
}
